/**
 * Input Validator and Sanitizer
 *
 * Comprehensive input validation and sanitization for security
 */

const crypto = require('crypto');

class InputValidator {
    constructor(options = {}) {
        this.options = {
            maxStringLength: options.maxStringLength || 10000,
            maxArrayLength: options.maxArrayLength || 1000,
            allowedImageFormats: options.allowedImageFormats || ['jpeg', 'jpg', 'png', 'webp', 'gif'],
            maxImageSize: options.maxImageSize || 10 * 1024 * 1024, // 10MB
            maxUrlLength: options.maxUrlLength || 2048,
            enableDeepValidation: options.enableDeepValidation !== false,
            ...options
        };

        // Define validation schemas
        this.schemas = {
            analyze_image: {
                required: ['imgString', 'prompt'],
                optional: [],
                fields: {
                    imgString: {
                        type: 'string',
                        maxLength: this.options.maxImageSize * 1.5, // Base64 encoded
                        pattern: /^(data:image\/[a-zA-Z]+;base64,)|(https?:\/\/)|(\/.*)/i,
                        sanitize: true
                    },
                    prompt: {
                        type: 'string',
                        minLength: 1,
                        maxLength: this.options.maxStringLength,
                        sanitize: true,
                        allowedTags: ['b', 'i', 'em', 'strong']
                    }
                }
            },
            use_model: {
                required: ['providerModel'],
                optional: ['model'],
                fields: {
                    providerModel: {
                        type: 'string',
                        pattern: /^[a-zA-Z0-9_-]+-[a-zA-Z0-9_-]+$|^[a-zA-Z0-9_-]+$/,
                        maxLength: 100,
                        sanitize: true
                    },
                    model: {
                        type: 'string',
                        maxLength: 100,
                        sanitize: true
                    }
                }
            },
            config_provider: {
                required: [],
                optional: [],
                fields: {}
            }
        };
    }

    /**
     * Validate and sanitize input
     */
    validate(operation, data) {
        try {
            const schema = this.schemas[operation];
            if (!schema) {
                return {
                    valid: false,
                    errors: [`Unknown operation: ${operation}`],
                    sanitizedData: null
                };
            }

            const errors = [];
            const sanitizedData = {};
            const warnings = [];

            // Check required fields
            for (const field of schema.required) {
                if (!(field in data)) {
                    errors.push(`Missing required field: ${field}`);
                }
            }

            // Validate and sanitize each field
            const allFields = [...schema.required, ...schema.optional];
            for (const field of allFields) {
                if (field in data) {
                    const validation = this.validateField(field, data[field], schema.fields[field]);

                    if (!validation.valid) {
                        errors.push(...validation.errors);
                    } else {
                        sanitizedData[field] = validation.sanitized;
                        if (validation.warnings && validation.warnings.length > 0) {
                            warnings.push(...validation.warnings);
                        }
                    }
                }
            }

            // Check for unexpected fields
            const unexpectedFields = Object.keys(data).filter(field => !allFields.includes(field));
            if (unexpectedFields.length > 0) {
                warnings.push(`Unexpected fields: ${unexpectedFields.join(', ')}`);
            }

            // Deep validation if enabled (check original data before sanitization)
            if (this.options.enableDeepValidation) {
                const deepValidation = this.performDeepValidation(operation, data);
                if (!deepValidation.valid) {
                    errors.push(...deepValidation.errors);
                }
                if (deepValidation.warnings) {
                    warnings.push(...deepValidation.warnings);
                }
            }

            return {
                valid: errors.length === 0,
                errors,
                warnings,
                sanitized: sanitizedData,
                sanitizedData
            };

        } catch (error) {
            return {
                valid: false,
                errors: [`Validation error: ${error.message}`],
                sanitizedData: null
            };
        }
    }

    /**
     * Validate individual field
     */
    validateField(fieldName, value, fieldSchema) {
        const result = {
            valid: true,
            errors: [],
            sanitized: value,
            warnings: []
        };

        try {
            // Handle null/undefined values for optional fields (before type validation)
            if (value === null || value === undefined) {
                result.sanitized = value;
                return result;
            }

            // Type validation
            if (!this.validateType(value, fieldSchema.type)) {
                result.valid = false;
                result.errors.push(`Invalid type for ${fieldName}: expected ${fieldSchema.type}, got ${typeof value}`);
                return result;
            }

            // String validation
            if (fieldSchema.type === 'string') {
                const stringValidation = this.validateString(fieldName, value, fieldSchema);
                if (!stringValidation.valid) {
                    result.valid = false;
                    result.errors.push(...stringValidation.errors);
                }
                result.sanitized = stringValidation.sanitized;
                if (stringValidation.warnings) {
                    result.warnings.push(...stringValidation.warnings);
                }
            }

            // Array validation
            else if (fieldSchema.type === 'array') {
                const arrayValidation = this.validateArray(fieldName, value, fieldSchema);
                if (!arrayValidation.valid) {
                    result.valid = false;
                    result.errors.push(...arrayValidation.errors);
                }
                result.sanitized = arrayValidation.sanitized;
                if (arrayValidation.warnings && arrayValidation.warnings.length > 0) {
                    result.warnings.push(...arrayValidation.warnings);
                }
            }

            // Object validation
            else if (fieldSchema.type === 'object') {
                const objectValidation = this.validateObject(fieldName, value, fieldSchema);
                if (!objectValidation.valid) {
                    result.valid = false;
                    result.errors.push(...objectValidation.errors);
                }
                result.sanitized = objectValidation.sanitized;
            }

            // Pattern validation
            if (fieldSchema.pattern && typeof result.sanitized === 'string') {
                if (!fieldSchema.pattern.test(result.sanitized)) {
                    result.valid = false;
                    result.errors.push(`Invalid format for ${fieldName}`);
                }
            }

            return result;

        } catch (error) {
            result.valid = false;
            result.errors.push(`Field validation error for ${fieldName}: ${error.message}`);
            return result;
        }
    }

    /**
     * Validate string fields
     */
    validateString(fieldName, value, schema) {
        const result = {
            valid: true,
            errors: [],
            sanitized: value,
            warnings: []
        };

        // Length validation
        if (schema.minLength && value.length < schema.minLength) {
            result.valid = false;
            result.errors.push(`${fieldName} must be at least ${schema.minLength} characters`);
        }

        if (schema.maxLength && value.length > schema.maxLength) {
            result.warnings.push(`${fieldName} truncated to ${schema.maxLength} characters`);
            value = value.substring(0, schema.maxLength);
            result.sanitized = value;
        }

        // Sanitization
        if (schema.sanitize) {
            value = this.sanitizeString(value, schema);
        }

        // Image data validation
        if (fieldName === 'imgString') {
            const imageValidation = this.validateImageData(value);
            if (!imageValidation.valid) {
                result.valid = false;
                result.errors.push(...imageValidation.errors);
            }
            if (imageValidation.warnings) {
                result.warnings.push(...imageValidation.warnings);
            }
        }

        // URL validation
        if (schema.type === 'url') {
            const urlValidation = this.validateUrl(value);
            if (!urlValidation.valid) {
                result.valid = false;
                result.errors.push(...urlValidation.errors);
            }
        }

        result.sanitized = value;
        return result;
    }

    /**
     * Validate array fields
     */
    validateArray(fieldName, value, schema) {
        const result = {
            valid: true,
            errors: [],
            sanitized: value,
            warnings: []
        };

        if (!Array.isArray(value)) {
            result.valid = false;
            result.errors.push(`${fieldName} must be an array`);
            return result;
        }

        if (schema.maxLength && value.length > schema.maxLength) {
            result.warnings.push(`${fieldName} truncated to ${schema.maxLength} items`);
            value = value.slice(0, schema.maxLength);
            result.sanitized = value;
        }

        // Validate array items if schema provided
        if (schema.items) {
            const sanitizedItems = [];
            for (let i = 0; i < value.length; i++) {
                const itemValidation = this.validateField(`${fieldName}[${i}]`, value[i], schema.items);
                if (!itemValidation.valid) {
                    result.valid = false;
                    result.errors.push(...itemValidation.errors);
                } else {
                    sanitizedItems.push(itemValidation.sanitized);
                    if (itemValidation.warnings && itemValidation.warnings.length > 0) {
                        result.warnings.push(...itemValidation.warnings);
                    }
                }
            }
            result.sanitized = sanitizedItems;
        }

        return result;
    }

    /**
     * Validate object fields
     */
    validateObject(fieldName, value, schema) {
        const result = {
            valid: true,
            errors: [],
            sanitized: value,
            warnings: []
        };

        if (typeof value !== 'object' || value === null || Array.isArray(value)) {
            result.valid = false;
            result.errors.push(`${fieldName} must be an object`);
            return result;
        }

        // Validate object properties if schema provided
        if (schema.properties) {
            const sanitizedObj = {};
            for (const [propName, propSchema] of Object.entries(schema.properties)) {
                if (propName in value) {
                    const propValidation = this.validateField(`${fieldName}.${propName}`, value[propName], propSchema);
                    if (!propValidation.valid) {
                        result.valid = false;
                        result.errors.push(...propValidation.errors);
                    } else {
                        sanitizedObj[propName] = propValidation.sanitized;
                        if (propValidation.warnings) {
                            result.warnings.push(...propValidation.warnings);
                        }
                    }
                }
            }
            result.sanitized = sanitizedObj;
        }

        return result;
    }

    /**
     * Validate image data
     */
    validateImageData(imageData) {
        const result = {
            valid: true,
            errors: [],
            warnings: []
        };

        try {
            // Check if it's a base64 encoded image
            if (imageData.startsWith('data:image/')) {
                const matches = imageData.match(/^data:(image\/[a-zA-Z]+);base64,(.+)$/);
                if (!matches) {
                    result.valid = false;
                    result.errors.push('Invalid data URL format');
                    return result;
                }

                const mimeType = matches[1];
                const base64Data = matches[2];

                // Check supported image formats
                const format = mimeType.split('/')[1];
                if (!this.options.allowedImageFormats.includes(format)) {
                    result.valid = false;
                    result.errors.push(`Unsupported image format: ${format}`);
                    return result;
                }

                // Check image size
                const imageSize = Buffer.byteLength(base64Data, 'base64');
                if (imageSize > this.options.maxImageSize) {
                    result.valid = false;
                    result.errors.push(`Image too large: ${imageSize} bytes (max: ${this.options.maxImageSize})`);
                    return result;
                }

                // Validate base64
                if (!this.isValidBase64(base64Data)) {
                    result.valid = false;
                    result.errors.push('Invalid base64 encoding');
                    return result;
                }

            } else if (imageData.startsWith('http')) {
                // URL validation
                const urlValidation = this.validateUrl(imageData);
                if (!urlValidation.valid) {
                    result.valid = false;
                    result.errors.push(...urlValidation.errors);
                }

            } else if (imageData.startsWith('/')) {
                // File path validation
                if (!this.isValidFilePath(imageData)) {
                    result.valid = false;
                    result.errors.push('Invalid file path');
                }
            } else {
                result.valid = false;
                result.errors.push('Invalid image data format');
            }

        } catch (error) {
            result.valid = false;
            result.errors.push(`Image validation error: ${error.message}`);
        }

        return result;
    }

    /**
     * Validate URL
     */
    validateUrl(url) {
        const result = {
            valid: true,
            errors: []
        };

        if (url.length > this.options.maxUrlLength) {
            result.valid = false;
            result.errors.push(`URL too long`);
            return result;
        }

        try {
            const urlObj = new URL(url);

            // Only allow HTTP/HTTPS
            if (!['http:', 'https:'].includes(urlObj.protocol)) {
                result.valid = false;
                result.errors.push('Only HTTP and HTTPS URLs are allowed');
                return result;
            }

            // Prevent localhost in production
            if (process.env.NODE_ENV === 'production') {
                const hostname = urlObj.hostname.toLowerCase();
                if (hostname === 'localhost' || hostname.startsWith('127.') || hostname.startsWith('192.168.')) {
                    result.valid = false;
                    result.errors.push('Local network URLs are not allowed in production');
                }
            }

        } catch (error) {
            result.valid = false;
            result.errors.push('Invalid URL format');
        }

        return result;
    }

    /**
     * Validate file path
     */
    isValidFilePath(filePath) {
        // Prevent path traversal
        if (filePath.includes('..') || filePath.includes('~')) {
            return false;
        }

        // Only allow specific patterns
        const allowedPatterns = [
            /^\/[a-zA-Z0-9_\-\/\.]+\.(jpg|jpeg|png|gif|webp)$/i,
            /^[a-zA-Z0-9_\-\/\.]+\.(jpg|jpeg|png|gif|webp)$/i
        ];

        return allowedPatterns.some(pattern => pattern.test(filePath));
    }

    /**
     * Perform deep validation
     */
    performDeepValidation(operation, data) {
        const result = {
            valid: true,
            errors: [],
            warnings: []
        };

        try {
            // Operation-specific deep validation
            switch (operation) {
                case 'analyze_image':
                    return this.deepValidateAnalyzeImage(data);
                case 'use_model':
                    return this.deepValidateUseModel(data);
                default:
                    return result;
            }

        } catch (error) {
            result.valid = false;
            result.errors.push(`Deep validation error: ${error.message}`);
            return result;
        }
    }

    /**
     * Deep validation for analyze_image
     */
    deepValidateAnalyzeImage(data) {
        const result = {
            valid: true,
            errors: [],
            warnings: []
        };

        // Check prompt for malicious content
        const maliciousPatterns = [
            /<script/i,
            /javascript:/i,
            /on\w+\s*=/i,
            /eval\s*\(/i,
            /exec\s*\(/i
        ];

        for (const pattern of maliciousPatterns) {
            if (pattern.test(data.prompt)) {
                result.valid = false;
                result.errors.push('Prompt contains potentially malicious content');
                break;
            }
        }

        // Check prompt length reasonableness
        if (data.prompt.length < 3) {
            result.warnings.push('Prompt is very short, may not provide useful results');
        } else if (data.prompt.length > 2000) {
            result.warnings.push('Prompt is very long, may be truncated by the AI model');
        }

        return result;
    }

    /**
     * Deep validation for use_model
     */
    deepValidateUseModel(data) {
        const result = {
            valid: true,
            errors: [],
            warnings: []
        };

        // Validate provider-model format
        const providerModelParts = data.providerModel.split('-');
        if (providerModelParts.length < 2) {
            result.warnings.push('Provider-model format may be incomplete');
        }

        // Check for common provider names
        const knownProviders = ['tencent', 'zai', 'moonshot', 'openai', 'anthropic'];
        const provider = providerModelParts[0];

        if (!knownProviders.includes(provider)) {
            result.warnings.push(`Unknown provider: ${provider}`);
        }

        return result;
    }

    /**
     * Validate type
     */
    validateType(value, expectedType) {
        switch (expectedType) {
            case 'string':
                return typeof value === 'string';
            case 'number':
                return typeof value === 'number' && !isNaN(value);
            case 'boolean':
                return typeof value === 'boolean';
            case 'array':
                return Array.isArray(value);
            case 'object':
                return typeof value === 'object' && value !== null && !Array.isArray(value);
            case 'url':
                return typeof value === 'string';
            default:
                return true;
        }
    }

    /**
     * Sanitize string
     */
    sanitizeString(str, schema) {
        // Remove control characters
        str = str.replace(/[\x00-\x1F\x7F]/g, '');

        // Remove null bytes
        str = str.replace(/\0/g, '');

        // Trim whitespace
        str = str.trim();

        // HTML sanitization if allowed tags specified
        if (schema.allowedTags && schema.allowedTags.length > 0) {
            str = this.sanitizeHTML(str, schema.allowedTags);
        } else {
            // Remove all HTML tags
            str = str.replace(/<[^>]*>/g, '');
        }

        // Normalize Unicode
        str = str.normalize('NFC');

        return str;
    }

    /**
     * Sanitize HTML with allowed tags
     */
    sanitizeHTML(html, allowedTags) {
        // Simple HTML sanitizer - in production, consider using a library like DOMPurify
        const allowedTagPattern = new RegExp(`^<\/?(${allowedTags.join('|')})(\\s[^>]*)?>$`, 'i');

        // Remove script tags and dangerous attributes
        return html
            .replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '')
            .replace(/on\w+\s*=\s*["'][^"']*["']/gi, '')
            .replace(/javascript:/gi, '')
            .replace(/<[^>]*>/g, (match) => {
                return allowedTagPattern.test(match) ? match : '';
            })
            .replace(/\s+/g, ' ')
            .trim();
    }

    /**
     * Check if string is valid base64
     */
    isValidBase64(str) {
        try {
            return Buffer.from(str, 'base64').toString('base64') === str;
        } catch {
            return false;
        }
    }

    /**
     * Get validation statistics
     */
    getValidationStats() {
        return {
            schemas: Object.keys(this.schemas).length,
            options: this.options,
            supportedFormats: this.options.allowedImageFormats,
            maxImageSize: this.options.maxImageSize
        };
    }

    /**
     * Add custom validation schema
     */
    addSchema(operation, schema) {
        this.schemas[operation] = schema;
    }

    /**
     * Remove validation schema
     */
    removeSchema(operation) {
        delete this.schemas[operation];
    }
}

module.exports = InputValidator;